package tom.spring.cloud.consumer.service;

import com.netflix.hystrix.*;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.servo.util.Strings;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

/**
 * 通过HystrixCollapser实现请求合并, 请求合并适用于请求耗时较长并请求次数频繁的服务，对于响应时间小于10ms的影响性能比较严重，
 * 对于每个时间窗口请求数量很少的不适合做合并
 *  1. 编码方式实现
 *  2. 注解方式实现
 * @author ZHUFEIFEI
 */
@Slf4j
@Service
public class HelloService2 {

    @Autowired
    private RestTemplate restTemplate;

    public String helloWithCode(String name) throws ExecutionException, InterruptedException {
        HelloCollapserCommand cmd = new HelloCollapserCommand(this.restTemplate, name);
        return cmd.queue().get();
    }

    /**
     * GLOBAL对所有的请求线程起效，REQUEST只对每次请求的单个线程的多次调用合并
     * {@link HystrixCollapserProperties}
     * @param name
     * @return
     */
    @com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser(
            batchMethod = "helloAll",
            scope = HystrixCollapser.Scope.GLOBAL,
            collapserProperties = {
                    @HystrixProperty(name = "timerDelayInMilliseconds", value = "5000")
            }
    )
    public String helloWithAnnotation(String name) {
        return null;
    }

    @com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand
    public List<String> helloAll(List<String> names) {
        log.info("collapse name : {}", Strings.join(",", names.iterator()));
        return this.restTemplate.exchange("http://provider01/hello/all?names={1}", HttpMethod.GET
                , null, List.class, Strings.join(",", names.iterator())).getBody();
    }

}

/**
 *
 */
@Slf4j
class BatchHelloCommand extends HystrixCommand<List<String>> {

    private RestTemplate restTemplate;
    private List<String> names;

    public BatchHelloCommand(RestTemplate restTemplate, List<String> names) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("hello_group_key")));
        this.restTemplate = restTemplate;
        this.names = names;
    }


    @Override
    protected List<String> run() throws Exception {
        log.info("collapse name : {}", Strings.join(",", names.iterator()));
        return this.restTemplate.exchange("http://provider01/hello/all?names={1}", HttpMethod.GET
                , null, List.class, Strings.join(",", names.iterator())).getBody();
    }
}

class HelloCollapserCommand extends HystrixCollapser<List<String>, String, String> {

    private RestTemplate restTemplate;
    private String name;

    public HelloCollapserCommand(RestTemplate restTemplate, String name) {
        super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("hello_collapser_key"))
                .andCollapserPropertiesDefaults(
                        //设置合并1秒内的请求，默认是10ms
                        HystrixCollapserProperties.Setter().withTimerDelayInMilliseconds(5000)
                )
                //全局请求合并生效，默认REQUEST只对每次请求中的多次调用生效
                .andScope(Scope.GLOBAL)
        );
        this.restTemplate = restTemplate;
        this.name = name;
    }

    @Override
    public String getRequestArgument() {
        //每个请求对应的参数值
        return this.name;
    }

    @Override
    protected HystrixCommand<List<String>> createCommand(Collection<CollapsedRequest<String, String>> collapsedRequests) {
        List<String> names = new ArrayList<>(collapsedRequests.size());
        //所有请求参数收集起来
        names.addAll(collapsedRequests.stream().map(CollapsedRequest::getArgument).collect(Collectors.toList()));
        return new BatchHelloCommand(this.restTemplate, names);
    }

    @Override
    protected void mapResponseToRequests(List<String> batchResponse, Collection<CollapsedRequest<String, String>> collapsedRequests) {
        int index = 0;
        //响应数据设置给每个请求
        for (CollapsedRequest<String, String> req : collapsedRequests) {
            req.setResponse(batchResponse.get(index ++));
        }
    }
}
